package top.someapp.applib.io;

import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.attribute.FileTime;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import top.someapp.applib.ext.DateTime;

/**
 * @author zw-zheng
 * Created on 2019-07-04
 */
@SuppressWarnings("ResultOfMethodCallIgnored")
public class IOUtil {

    private static void walkDir(String basePath, File dir, ZipOutputStream zipOut)
        throws IOException {
        for (File file : dir.listFiles()) {
            ZipEntry entry;
            if (file.isDirectory()) {
                entry = new ZipEntry(basePath + file.getName() + "/");
            } else if (file.isFile()) {
                entry = new ZipEntry(basePath + file.getName());
            } else {
                throw new IOException(file.getName() + " is not a file or directory!");
            }
            if (VERSION.SDK_INT >= VERSION_CODES.O) {
                entry.setLastModifiedTime(FileTime.fromMillis(file.lastModified()));
            }

            zipOut.putNextEntry(entry);
            if (entry.isDirectory()) {
                walkDir(entry.getName(), file, zipOut);
            } else {
                zipOut.write(readAllBytes(file));
                zipOut.flush();
            }
        }
    }

    public static boolean hasSubDir(File parent, final String sub) {
        if (!parent.exists() || parent.isFile()) {
            return false;
        }
        File[] subDirs = parent.listFiles(new FileFilter() {
            @Override
            public boolean accept(File f) {
                return f.isDirectory() && f.getName().equals(sub);
            }
        });
        return subDirs.length == 1;
    }

    public static boolean hasFileOrDir(File parent, final String sub) {
        if (!parent.exists() || parent.isFile()) {
            return false;
        }
        File[] subDirs = parent.listFiles(new FileFilter() {
            @Override
            public boolean accept(File f) {
                return f.getName().equals(sub);
            }
        });
        return subDirs.length == 1;
    }

    public static File getSubDirIfExists(File parent, final String sub) {
        if (!parent.exists() || parent.isFile()) {
            return null;
        }
        File[] subDirs = parent.listFiles(new FileFilter() {
            @Override
            public boolean accept(File f) {
                return f.isDirectory() && f.getName().equals(sub);
            }
        });
        return subDirs[0];
    }

    public static byte[] readAllBytes(File file) throws IOException {
        FileInputStream ins = new FileInputStream(file);
        byte[] bytes = new byte[512];
        ByteArrayOutputStream out = new ByteArrayOutputStream(512);
        for (int len; ; ) {
            len = ins.read(bytes);
            if (len < 1) {
                break;
            }
            out.write(bytes, 0, len);
        }
        ins.close();
        return out.toByteArray();
    }

    public static void zipFilesInDir(File dir) throws IOException {
        String fileName = String
            .format("%s-%s.zip", dir.getName(), DateTime.now().dateTimeDigits());
        zipFilesInDir(dir, new File(dir.getParentFile(), fileName));
    }

    public static void zipFilesInDir(File dir, File dest) throws IOException {
        assert dir != null && dir.exists() && dir.isDirectory();
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(dest));
        walkDir("./", dir, out);
        out.close();
    }

    public static File unzip(File zip) throws IOException {
        File dir = new File(zip.getParentFile(),
            "unzip-" + DateTime.now().dateTimeDigits());
        unzipToDir(zip, dir);
        return dir;
    }

    public static void unzipToDir(File zip, File dir) throws IOException {
        assert zip != null && zip.isFile() && zip.exists() && dir != null;
        if (!dir.exists()) {
            dir.mkdirs();
        }

        ZipFile zipFile = new ZipFile(zip);
        Enumeration<? extends ZipEntry> entries = zipFile.entries();
        while (entries.hasMoreElements()) {
            ZipEntry zipEntry = entries.nextElement();
            File file = new File(dir, zipEntry.getName());
            if (zipEntry.isDirectory()) {
                file.mkdirs();
            } else {
                InputStream ins = zipFile.getInputStream(zipEntry);
                file.createNewFile();
                copyTo(ins, file);
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                file.setLastModified(zipEntry.getLastModifiedTime().toMillis());
            }
        }
    }

    public static void copyTo(InputStream ins, File file) throws IOException {
        OutputStream out = new FileOutputStream(file);
        byte[] bytes = new byte[32 * 1024];
        for (int len; ; ) {
            len = ins.read(bytes);
            if (len < 1) {
                break;
            }
            out.write(bytes, 0, len);
        }
        out.close();
        ins.close();
    }

    private static void makeDirIf(File dir) {
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    private static void doDirCopy(File dir, File to) throws IOException {
        makeDirIf(to);
        for (File file : dir.listFiles()) {
            File newFile = new File(to, file.getName());
            if (file.isFile()) {
                copyTo(new FileInputStream(file), newFile);
            } else if (file.isDirectory()) {
                doDirCopy(file, newFile);
            }
            newFile.setLastModified(file.lastModified());
        }
        to.setLastModified(dir.lastModified());
    }

    public static void copyDir(File from, File to) throws IOException {
        if (from == null || !from.isDirectory() || to == null) {
            return;
        }
        doDirCopy(from, to);
    }

    public static void moveToDir(File parentDir, File toDir, FileFilter filter) throws IOException {
        assert toDir != null && parentDir.exists() && parentDir.isDirectory();
        if (!toDir.exists()) {
            toDir.mkdirs();
        }

        for (File f : parentDir.listFiles()) {
            if (filter.accept(f)) {
                if (f.isFile()) {
                    copyTo(new FileInputStream(f), new File(toDir, f.getName()));
                } else if (f.isDirectory()) {
                    File newDir = new File(toDir, f.getName());
                    newDir.mkdirs();
                    moveToDir(f, newDir, filter);
                }
            }
            f.delete();
        }
    }
}
